import heapq, random, sys

GOAL_BOARD = None 
heuristic = None

def main():
  global GOAL_BOARD, heuristic
  GOAL_BOARD = new_board()
  puzzle_board = new_board()
  
  move_routine = None
  number_of_moves = 5
  heuristic = None
  
  num_args = len(sys.argv)
  arg_ctr = 0
  while arg_ctr < num_args:
    arg = sys.argv[arg_ctr]
    if arg == '-m':
      move_routine = sys.argv[arg_ctr+1]
      arg_ctr += 1
    elif arg == '-n':
      number_of_moves = sys.argv[arg_ctr+1]
      arg_ctr += 1
    elif arg == '-h':
      heuristic = sys.argv[arg_ctr+1]
      arg_ctr += 1
    arg_ctr += 1
  
  if move_routine == 'seven':
    moves = []
    move_string = '[((2, 3) to (3, 3)), ((1, 3) to (2, 3)), ((0, 3) to (1, 3)), ((0, 2) to (0, 3)), ((1, 2) to (0, 2)), ((2, 2) to (1, 2)), ((2, 1) to (2, 2))]'
    move_string = move_string[3:-3]
    move_array = move_string.split(')), ((')
    for m in move_array:
      pos_array = m.split(') to (')
      str_arr0 = pos_array[0].split(', ')
      pos0 = int(str_arr0[0]), int(str_arr0[1])
      str_arr1 = pos_array[1].split(', ')
      pos1 = int(str_arr1[0]), int(str_arr1[1])
      moves.append(new_move(pos0, pos1))
    for m in moves:
      puzzle_board.move(m)
  else:
    ctr = 0
    visited = Counter()
    moves = []
    while ctr < int(number_of_moves):
      ctr += 1
      visited[str(puzzle_board)] += 1
      next_move = random.choice(puzzle_board.get_moves())
      puzzle_board.move(next_move)
      if 0 < visited[str(puzzle_board)] < 10:
        puzzle_board.move(next_move.reverse())
        ctr -= 1
      else:
        moves.append(next_move)
  
  print '%d moves' % len(moves)
  print moves
  print puzzle_board
  solution = solve(state_from_board(board=puzzle_board, path=[]))
  print '%d moves' % len(solution)
  print solution

def solve(initial_state):
  fringe = PriorityQueue()
  visited = []
  fringe.push(initial_state, g(initial_state) + h(initial_state))
  while not fringe.is_empty():
    state = fringe.pop()
    if state.board in visited:
      continue
    visited.append(state.board)
    if is_goal(state):
      return state.path
    new_moves = state.board.get_moves()
    new_states = []
    for move in new_moves:
      new_path = list(state.path) + [move]
      new_board = state.board.copy()
      new_board.move(move)
      new_state = state_from_board(new_board, new_path)
      fringe.push(new_state, g(state) + h(state))
  return None

def g(state):
  return len(state.path)

def h(state):
  global heuristic
  def square_home(number):
    if number is None:
      return (3,3)
    x = (number - 1) % 4
    y = (number - 1) / 4
    return (x,y)
  def manhattan(pos1, pos2):
    return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1])
  
  if heuristic == 'manhattan':
    board = state.board
    distance = 0
    for x in xrange(4):
      for y in xrange(4):
        square = board.squares[(x,y)]
        distance += manhattan((x,y), square_home(square.value))
    return distance
  else:
    return 0

def is_goal(state):
  global GOAL_BOARD
  board = state.board
  return str(board) == str(GOAL_BOARD)

def state_from_board(board, path):
  return PuzzleState(board=board, path=path)

def new_board():
  return Board()

class Board():
  def __init__(self):
    self.squares = {}
    self.initialize()
  
  def copy(self):
    new_board = Board()
    new_board.squares = self.squares.copy()
    for k,v in new_board.squares.items():
      new_board.squares[k] = v.copy_without_neighbors()
    new_board.construct_neighbors()
    return new_board

  def initialize(self):
    self.squares = {}
    for i in xrange(1,16):
      x = (i - 1) % 4
      y = (i - 1) / 4
      self.squares[(x,y)] = new_square(i, (x,y))
    self.squares[(3,3)] = new_blank((3,3))
    self.construct_neighbors()
  
  def construct_neighbors(self):
    for x in xrange(4):
      for y in xrange(4):
        square = self.squares[(x,y)]
        square.neighbors = []
        difs = [(1,0), (0,1), (-1,0), (0,-1)]
        for dx,dy in difs:
          nx, ny = x + dx, y + dy
          if 0 <= nx <= 3 and 0 <= ny <= 3:
            square.neighbors.append(self.squares[(nx,ny)])
  
  def __str__(self):
    output = ''
    for y in xrange(4):
      for x in xrange(4):
        output += str(self.squares[(x,y)])
        if x < 3:
          output += ' '
      if y < 3:
        output += '\n'
    return output
  
  def get_blanks(self):
    blanks = [square for square in self.squares.values() if square.is_blank()]
    return blanks

  def move(self, move):
    square_pos, blank_pos = move.square_pos, move.blank_pos
    blank = self.squares[blank_pos].value
    assert blank is None, blank
    self.squares[blank_pos].value = self.squares[square_pos].value
    self.squares[square_pos].value = blank
  
  def get_moves(self):
    moves = []
    for blank in self.get_blanks():
      for neighbor in blank.neighbors:
        if not neighbor.is_blank():
          moves.append(new_move(neighbor.pos, blank.pos))
    return moves

def new_square(value, pos):
  return Square(value, pos)

def new_blank(pos):
  return Square(None, pos)

class Square():
  def __init__(self, value, pos, neighbors=None):
    if neighbors is None:
      neighbors = []
    self.neighbors = neighbors
    self.value = value
    self.pos = pos
  
  def is_blank(self):
    return self.value is None
  
  def __str__(self):
    if self.value is None:
      output = ''
    else:
      output = self.value
    return str(output).rjust(3)

  def __repr__(self):
    return str(self)

  def copy_without_neighbors(self):
    return new_square(self.value, self.pos)

def new_move(square_pos, blank_pos):
  return Move(square_pos, blank_pos)

class Move():
  def __init__(self, square_pos, blank_pos):
    self.square_pos = square_pos
    self.blank_pos = blank_pos
  
  def reverse(self):
    return new_move(self.blank_pos, self.square_pos)
    
  def __str__(self):
    return '(%s to %s)' % (str(self.square_pos), str(self.blank_pos))
  
  def __repr__(self):
    return str(self)

class PuzzleState():
  def __init__(self, board=None, path=None):
    self.board = board
    self.path = path
  
  def __str__(self):
    output = 'Path: '
    if self.path is not None:
      output += str(self.path)
    output += '\n'
    if self.board is not None:
      output += str(self.board)
    return output
  
  def __repr__(self):
    return str(self)

class PriorityQueue():
  def __init__(self):
    self.heap = []
  
  def push(self, item, value):
    heapq.heappush(self.heap, (value, item))
  
  def pop(self):
    blob = heapq.heappop(self.heap)
    return blob[1]
  
  def is_empty(self):
    return len(self.heap) == 0
  
  def __str__(self):
    return str(self.heap)

  def __repr__(self):
    return str(self)


class Counter(dict):
  """
  A counter keeps track of counts for a set of keys.
  
  The counter class is an extension of the standard python
  dictionary type.  It is specialized to have number values  
  (integers or floats), and includes a handful of additional
  functions to ease the task of counting data.  In particular, 
  all keys are defaulted to have value 0.  Using a dictionary:
  
  a = {}
  print a['test']
  
  would give an error, while the Counter class analogue:
    
  >>> a = Counter()
  >>> print a['test']
  0

  returns the default 0 value. Note that to reference a key 
  that you know is contained in the counter, 
  you can still use the dictionary syntax:
    
  >>> a = Counter()
  >>> a['test'] = 2
  >>> print a['test']
  2
  
  This is very useful for counting things without initializing their counts,
  see for example:
  
  >>> a['blah'] += 1
  >>> print a['blah']
  1
  
  The counter also includes additional functionality useful in implementing
  the classifiers for this assignment.  Two counters can be added,
  subtracted or multiplied together.  See below for details.  They can
  also be normalized and their total count and arg max can be extracted.
  """
  def __getitem__(self, idx):
    self.setdefault(idx, 0)
    return dict.__getitem__(self, idx)

  def incrementAll(self, keys, count):
    """
    Increments all elements of keys by the same count.
    
    >>> a = Counter()
    >>> a.incrementAll(['one','two', 'three'], 1)
    >>> a['one']
    1
    >>> a['two']
    1
    """
    for key in keys:
      self[key] += count
  
  def argMax(self):
    """
    Returns the key with the highest value.
    """
    if len(self.keys()) == 0: return None
    all = self.items()
    values = [x[1] for x in all]
    maxIndex = values.index(max(values))
    return all[maxIndex][0]
  
  def sortedKeys(self):
    """
    Returns a list of keys sorted by their values.  Keys
    with the highest values will appear first.
    
    >>> a = Counter()
    >>> a['first'] = -2
    >>> a['second'] = 4
    >>> a['third'] = 1
    >>> a.sortedKeys()
    ['second', 'third', 'first']
    """
    sortedItems = self.items()
    compare = lambda x, y:  sign(y[1] - x[1])
    sortedItems.sort(cmp=compare)
    return [x[0] for x in sortedItems]
  
  def totalCount(self):
    """
    Returns the sum of counts for all keys.
    """
    return sum(self.values())
  
  def normalize(self):
    """
    Edits the counter such that the total count of all
    keys sums to 1.  The ratio of counts for all keys
    will remain the same. Note that normalizing an empty 
    Counter will result in an error.
    """
    total = float(self.totalCount())
    if total == 0: return
    for key in self.keys():
      self[key] = self[key] / total
      
  def divideAll(self, divisor):
    """
    Divides all counts by divisor
    """
    divisor = float(divisor)
    for key in self:
      self[key] /= divisor

  def copy(self):
    """
    Returns a copy of the counter
    """
    return Counter(dict.copy(self))
  
  def __mul__(self, y ):
    """
    Multiplying two counters gives the dot product of their vectors where
    each unique label is a vector element.
    
    >>> a = Counter()
    >>> b = Counter()
    >>> a['first'] = -2
    >>> a['second'] = 4
    >>> b['first'] = 3
    >>> b['second'] = 5
    >>> a['third'] = 1.5
    >>> a['fourth'] = 2.5
    >>> a * b
    14
    """
    sum = 0
    x = self
    if len(x) > len(y):
      x,y = y,x
    for key in x:
      if key not in y:
        continue
      sum += x[key] * y[key]      
    return sum
      
  def __radd__(self, y):
    """
    Adding another counter to a counter increments the current counter
    by the values stored in the second counter.
    
    >>> a = Counter()
    >>> b = Counter()
    >>> a['first'] = -2
    >>> a['second'] = 4
    >>> b['first'] = 3
    >>> b['third'] = 1
    >>> a += b
    >>> a['first']
    1
    """ 
    for key, value in y.items():
      self[key] += value   
      
  def __add__( self, y ):
    """
    Adding two counters gives a counter with the union of all keys and
    counts of the second added to counts of the first.
    
    >>> a = Counter()
    >>> b = Counter()
    >>> a['first'] = -2
    >>> a['second'] = 4
    >>> b['first'] = 3
    >>> b['third'] = 1
    >>> (a + b)['first']
    1
    """
    addend = Counter()
    for key in self:
      if key in y:
        addend[key] = self[key] + y[key]
      else:
        addend[key] = self[key]
    for key in y:
      if key in self:
        continue
      addend[key] = y[key]
    return addend
    
  def __sub__( self, y ):
    """
    Subtracting a counter from another gives a counter with the union of all keys and
    counts of the second subtracted from counts of the first.
    
    >>> a = Counter()
    >>> b = Counter()
    >>> a['first'] = -2
    >>> a['second'] = 4
    >>> b['first'] = 3
    >>> b['third'] = 1
    >>> (a - b)['first']
    -5
    """      
    addend = Counter()
    for key in self:
      if key in y:
        addend[key] = self[key] - y[key]
      else:
        addend[key] = self[key]
    for key in y:
      if key in self:
        continue
      addend[key] = -1 * y[key]
    return addend

if __name__ == '__main__':
  main()
'''
state
  .board
    .get_blanks() = [square, square, ...]
  .path = [move, move, ...]

blank
  .neighbors

board_move(board, move)

state_from_board(board, path)
'''